package MainModel;

import java.awt.Point;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedList;

import VisibilityGraph.CrossCap;

import DataModel.CommonLibrary.CGeometricOperations;
import DataModel.CommonLibrary.CommonDefs.Layer;
import DataModel.ConnectivityManager.CConnectivityManager;
import DataModel.DesignRulesManager.CDesignRulesChecker;
import DataModel.DesignRulesManager.CDesignRulesManager;
import DataModel.DesignRulesManager.CDesignRulesViolation;
import DataModel.LayoutManager.CEdge;
import DataModel.LayoutManager.CLayoutManager;
import DataModel.LayoutManager.CPolygon;
import DataModel.LayoutManager.CRectangle;
import DataModel.LayoutParser.CLayoutParser;

public class CLIManager 
{
	/***************************************************/
	/****** Constants **********************************/
	/***************************************************/
	
	public final String USAGE = "Usage: java -jar LayoutSWPack.jar -l [layout file path] (-c [commands file])\n";
	
	// Commands length
	public final int CLI_COMMAND_MAX_LENGTH = 256;
	
	// Commands
	public final String DRC_CMD_STR = "drc";
	public final String BD_CMD_STR = "bd";
	public final String SPACE_CMD_STR = "space";
	public final String PERIM_CMD_STR = "perim";
	public final String UNION_CMD_STR = "union";
	public final String NEIGHB_CMD_STR = "neighb";
	public final String CROSSCAP_CMD_STR = "crosscap";
	public final String PLACE_CMD_STR = "place";
	public final String WHITE_CMD_STR = "white";
	public final String CONNECT_CMD_STR = "connect";
	public final String EXIT_CMD_STR = "exit";	
	public final String MAN_CMD_STR = "man";
	public final String PACKAGE_NAME = "Layout Managment SW Package";
	
	// Commands params
	public final int DRC_CMD_PARAMS = 1;
	public final int CROSSCAP_CMD_PARAMS = 1;
	public final int PLACE_CMD_PARAMS = 8;
	public final int WHITE_CMD_PARAMS = 4;
	public final int CONNECT_CMD_PARAMS = 4;

	/***************************************************/
	/****** Members ************************************/
	/***************************************************/
	private CLayoutParser m_parseManager;
	private CLayoutManager m_layoutManager;
	private CConnectivityManager m_connectivityManager;
	private CDesignRulesManager m_designRulesManager;
	private OutputStream m_printStream = System.out;
	
	
	/***************************************************/
	/****** C'tors *************************************/
	/***************************************************/
	public CLIManager()
	{}
	

	/***************************************************/
	/****** Public methods *****************************/
	/***************************************************/
	/**
	 * Handles main program arguments
	 * @param args - CLI arguments
	 * @return false on success
	 */
	public boolean ParseCommandLine(String[] args)
	{
		if(args.length < 2)
		{
			PrintMessage(USAGE);
			return true;
		}
		
		for(int i=0; i<args.length; i++)
		{
			switch(args[i].trim().toCharArray()[1])
			{
			case 'l':
				if(SetLayout(args[i+1]))
				{
					PrintMessage(USAGE);
					return true;
				}
				break;
			case 'c':
				if(!RunCommands(args[i+1]))
				{
					return true;
				}
				break;
			default:
				PrintMessage(USAGE);
				return true;				
			}
			
			i++;			
		}
		
		
		return false;
	}
	
	/**
	 * Using the Parse manager to initialize Layout and Connectivity managers 
	 * @param layoutPath - input layout file path
	 * @return false on success
	 */
	public boolean SetLayout(String layoutPath)
	{
		try {
			// PrintMessage("Layout loading - start");
			
			// Parse input
			m_parseManager = new CLayoutParser(layoutPath);		
			
			// Create Layout Manager
			m_layoutManager = new CLayoutManager(m_parseManager.getRecsList());

			// Create Connectivity manager
			m_connectivityManager = new CConnectivityManager(m_parseManager.getRecsList());
			
			// Create Design Rules manager
			m_designRulesManager = new CDesignRulesManager();
			
			// PrintMessage("Layout loading - done");
		} catch (RuntimeException e) {

			PrintMessage(e.getMessage());
			return true;
		}
		
		return false;
	}
	
	/**
	 * Responsible for interactive shell activation, commands parsing and task dispatching
	 */
	public void ActivateShell()
	{
		String command;
		
		PrintMessage("\n\n**** Welcome to " + PACKAGE_NAME + " ****");
		PrintMessage("\nPlease type 'man' for user manual\n");
		
		while(true)
		{
			command = ReadCommand();
					
			if(command == null || command.trim().length() < 1)
			{
				MakeBadCommand();
				continue;
			}
			
			if(DispatchCommand(command.trim()))
				return;
		}		
	}
	
	/**
	 * Activating shell for one command for debug and testing 
	 * @param command
	 */
	public boolean DebugShell(String command)
	{			
		if(command == null || command.trim().length() < 1)
		{
			MakeBadCommand();
			return false;
		}
		
		return DispatchCommand(command.trim());
	}	
	
	public boolean RunCommands(String filePath)
	{
		File file = new File(filePath);		
		URL fileURL = null;
		
		try 
		{
			fileURL = file.getAbsoluteFile().toURI().toURL();
			URLConnection urlCon = fileURL.openConnection();
            urlCon.setConnectTimeout(10000);
            urlCon.setReadTimeout(1000);
            
            InputStream inputStream = urlCon.getInputStream();
            InputStreamReader is = new InputStreamReader(inputStream);
	        LineNumberReader lineReader = new LineNumberReader(is);
            
			String line = lineReader.readLine();
			
			while(line != null)
			{
				if(DebugShell(line))
					return false;
				
				line = lineReader.readLine();
			}
			
			return true;
		}
		catch (Exception e) 
		{
			System.out.println("Could not find commands file");
			return false;
		}	
	}
	
	/***************************************************/
	/****** Utils ************************************/
	/***************************************************/	
	private boolean DispatchCommand(String command)
	{		
		switch(command.toLowerCase().charAt(0))
		{
		// Exit
		case 'e':
			return MakeExitCommand(command);
			
		// DRC (design rules check)	
		case 'd':
			return MakeDRCCommand(command);
		
		// BD (bounding box)
		case 'b':
			return MakeBDCommand(command);
			
		// Space (space of polygon)
		case 's':
			return MakeSpaceCommand(command);
			
		case 'p':
			if(command.length() < 2)
			{
				MakeBadCommand();
				return false;
			}
			
			switch(command.toLowerCase().charAt(1))
			{
			// Perim (perimeter of polygon)
			case 'e':
				return MakePerimCommand(command);
				
			// Place (placement of given box in geiven area)
			case 'l':
				return MakePlaceCommand(command);
				
			default:
				MakeBadCommand();
				return false; 
			}
			
		// Union (union of given set of polygons)
		case 'u':
			return MakeUnionCommand(command);
			
		// Neighb (neighbours of given polygon)
		case 'n':
			return MakeNeighbCommand(command);
			
		// White (white spaces in given area)
		case 'w':
			return MakeWhiteCommand(command);
			
		case 'c':
			if(command.length() < 2)
			{
				MakeBadCommand();
				return false;
			}
			
			switch(command.toLowerCase().charAt(1))
			{
			// Crosscap (cross capacitance of given layer)
			case 'r':
				return MakeCrosscapCommand(command);
				
			// Connect (connectivity check of two given points)
			case 'o':
				return MakeConnectCommand(command);
				
			default:
				MakeBadCommand();
				return false; 
			}
			
		// Man (user manual)
		case 'm':
			return MakeManCommand(command);
			
		default:
			MakeBadCommand();
			return false; 
		}
	}

	private String ReadCommand()
	{
		StringBuffer commandStr = new StringBuffer();
		byte[] commandArr = new byte[CLI_COMMAND_MAX_LENGTH];
		int bytesRead = 0;
		
		try 
		{	
			bytesRead = System.in.read(commandArr);
		} 
		catch (IOException e) 
		{	
			PrintMessage(e.getMessage());
			return null;
		}
		
		for (int i = 0; i < bytesRead-2; i++) 
		{
			commandStr.append((char)commandArr[i]);
		}
		
		return commandStr.toString();
	}
	
	private void PrintCommandResult(String command, String msg)
	{
		PrintMessage("\n#### " + command + " ################");
		PrintMessage(msg);
	}
	
	private void PrintMessage(String msg)
	{
		((PrintStream) m_printStream).println(msg);
	}
	
	private void PrintRectanglesList(LinkedList<CRectangle> recs)
	{
		if(recs == null)
			return;
		
		for (CRectangle rectangle : recs) 
			PrintMessage(rectangle.toString());
	}
	
	private boolean ValidateCommand(String input, String command)
	{
		if(input.length() >= command.length())
		{
			String[] params = input.split(" " , 0);
			
			if(params[0].equalsIgnoreCase(command))
				return true;
		}
		
		return false;
	}
	
	private String[] GetParams(String input, String saparator)
	{
		return input.split(saparator , 0);
	}
	
	private LinkedList<CPolygon> GetPolys(String command)
	{
		LinkedList<CPolygon> polyList = new LinkedList<CPolygon>();		
		
		String[] polys = command.trim().split(";");
		
		// iterate polys
		for (int i = 0; i < polys.length; i++) 
		{
			String polyStr = polys[i];
			String[] points = polyStr.trim().split(",");
			LinkedList<Point> pointsList = new LinkedList<Point>();
			
			// iterate points
			for (int j = 0; j < points.length; j++) 
			{
				String pointStr = points[j];
				String[] vals = pointStr.trim().split(" ");
				
				try {
					if(i==0 && j==0 && vals.length == 3)
						pointsList.add(new Point(Integer.valueOf(vals[1]),Integer.valueOf(vals[2])));
					else if(vals.length == 2)
						pointsList.add(new Point(Integer.valueOf(vals[0]),Integer.valueOf(vals[1])));
					else
						return null;
				} 
				catch (NumberFormatException e) {
					return null;
				}				
			}
			polyList.add(new CPolygon(pointsList));
		}
		
		return polyList;
	}
	
	/***************************************************/
	/****** Make commands ******************************/
	/***************************************************/
	private void MakeBadCommand()
	{
		PrintMessage("Bad command format. Type '" + MAN_CMD_STR + "' for user manual\n");
	}
	
	private boolean MakeExitCommand(String command)
	{
		if(ValidateCommand(command, EXIT_CMD_STR))
		{
			PrintMessage("\n\nThanks for using "+ PACKAGE_NAME +".\nGoodbye\n");
			return true;
		}

		MakeBadCommand();
		return false;
	}
	
	private boolean MakeDRCCommand(String command)
	{
		try 
		{		
			if(ValidateCommand(command, DRC_CMD_STR))
			{
				m_designRulesManager.RunFile(command.substring(DRC_CMD_STR.length() + 1, command.length()));
				
				CDesignRulesChecker drc = new CDesignRulesChecker(m_layoutManager,m_designRulesManager);
				
				LinkedList<CDesignRulesViolation> drcViolate = drc.activate();
				
				if(drcViolate == null)
					PrintCommandResult(DRC_CMD_STR, "Layout successfully passed DRC");
				else
				{
					PrintCommandResult(DRC_CMD_STR, "");
					for (CDesignRulesViolation designRulesViolation : drcViolate) 
						designRulesViolation.print();
				}
				
				return false;
			}
		} 
		catch (RuntimeException e) 
		{
			//PrintMessage("MakeDRCCommand\n" + e.getMessage());
		}
		
		MakeBadCommand();
		return false;
	}
	

	private boolean MakeConnectCommand(String command) 
	{
		try 
		{
			if(ValidateCommand(command, CONNECT_CMD_STR))
			{
				String params[] = command.split(";");

				if(params.length == CONNECT_CMD_PARAMS)
				{
					String coord1[] = params[0].trim().split(" ");
					Layer layer1 = Layer.values()[Integer.valueOf(params[1].trim())];
					String coord2[] = params[2].trim().split(" ");
					Layer layer2 = Layer.values()[Integer.valueOf(params[3].trim())];
					
					Point pnt1 = new Point(Integer.valueOf(coord1[1].trim()),Integer.valueOf(coord1[2].trim()));
					Point pnt2 = new Point(Integer.valueOf(coord2[0].trim()),Integer.valueOf(coord2[1].trim()));
					
					CRectangle rec1 = m_layoutManager.GetRecByPlace(pnt1,layer1);
					CRectangle rec2 = m_layoutManager.GetRecByPlace(pnt2,layer2);
						
					if(rec1 != null && rec2 != null)
					{
						String result;
						
						if(m_connectivityManager.AreConnected(rec1.getID(),rec2.getID()))
							result = " are connected";
						else
							result = " are NOT connected";
						
						PrintCommandResult(CONNECT_CMD_STR, "Box " + rec1.toString() + " and Box " + rec2.toString() + result);														
					}
					else						
						PrintCommandResult(CONNECT_CMD_STR, "Not inner point of a polygon");
					
					return false;
				}
			}
		} 
		catch (Exception e) 
		{}
		
		MakeBadCommand();		
		return false;
	}


	private boolean MakeCrosscapCommand(String command) 
	{
		if(ValidateCommand(command, CROSSCAP_CMD_STR))
		{
			String[] params = GetParams(command," ");
			
			try 
			{
				Layer inputLayer = Layer.values()[Integer.valueOf(params[1])];
				Double crossCapConst = Double.valueOf(params[3]);
				
				double result = CrossCap.Activate(m_layoutManager, inputLayer, crossCapConst);

				PrintCommandResult(CROSSCAP_CMD_STR, "Crosscapacitance of layer " + inputLayer.name() + " is " + result);
				return false;
			}
			catch (Exception e) 
			{}
		}
		
		MakeBadCommand();
		return false;
	}


	private boolean MakeWhiteCommand(String command) 
	{
		if(ValidateCommand(command, WHITE_CMD_STR))
		{	
			try 
			{
				String[] params = GetParams(command,";");
				
				CPolygon poly = GetPolys(params[0]).get(0);
				
				if(poly.IsRectangle())
				{
					Layer inputLayer = Layer.values()[Integer.valueOf(params[1].trim())];
					char direction = params[2].trim().toCharArray()[0];
					CRectangle area = poly.ToRectangles().get(0);
					
					if(direction == 'x' || direction == 'y')
					{												
						//return parameter
						LinkedList<CRectangle> freeArea = 
						WhiteSpaceReport.WhiteSpaceByRect.findWhiteSpaceByRect(
								area, 
								CGeometricOperations.GetRectanglesInArea(area,m_layoutManager.GetRecsInLayer(inputLayer)), 
								direction);
						
						PrintCommandResult(WHITE_CMD_STR, "");
						PrintRectanglesList(freeArea);
						return false;
					}
				}
			}
			catch (Exception e) 
			{}
		}
		
		MakeBadCommand();
		return false;
	}


	private boolean MakeNeighbCommand(String command) 
	{
		if(ValidateCommand(command, NEIGHB_CMD_STR))
		{
			try 
			{
				CPolygon poly = GetPolys(command).get(0);
				LinkedList<CRectangle> horNeighb = new LinkedList<CRectangle>();
				LinkedList<CRectangle> verNeighb = new LinkedList<CRectangle>();
				CRectangle inputRec = poly.ToRectangles().get(0);
				CRectangle origRec = m_layoutManager.GetRectangle(inputRec); 
				
				if(origRec == null)
				{
					PrintMessage(inputRec.toString() + " does not exist in given layout");
					return false;
				}
				
				horNeighb = m_layoutManager.GetNeighbours(origRec, 'x');
				verNeighb = m_layoutManager.GetNeighbours(origRec, 'y');
				
				PrintCommandResult(NEIGHB_CMD_STR, "");
				PrintMessage("--- Horizontal neighbours ---");
				PrintRectanglesList(horNeighb);
				
				PrintMessage("--- Vertical neighbours ---");
				PrintRectanglesList(verNeighb);
				
				return false;
			}
			catch (Exception e) 
			{}
		}
		
		MakeBadCommand();
		return false;
	}


	private boolean MakeUnionCommand(String command) 
	{
		if(ValidateCommand(command, UNION_CMD_STR))
		{
			try 
			{
				LinkedList<CPolygon> polysList = GetPolys(command);
				LinkedList<CRectangle> recsList = new LinkedList<CRectangle>();
				
				for (int i = 0; i < polysList.size(); i++) 
				{
					CPolygon poly = polysList.get(i);
					
					recsList.addAll(poly.ToRectangles());
				}
				
				LinkedList<CEdge> union = ScanLineAlghorithms.RectanglesUnion.RectanglesUnion.Activate(recsList);
								
				PrintCommandResult(UNION_CMD_STR, "--- Union Edges ---");
				for (CEdge edge : union) {
					PrintMessage(edge.ToString());
				}
				
				return false;
			}
			catch (Exception e) 
			{}
		}
		
		MakeBadCommand();
		return false;
	}


	private boolean MakePlaceCommand(String command) 
	{
		if(ValidateCommand(command, PLACE_CMD_STR))
		{
			try 
			{
				String[] params = GetParams(command,";");
				
				CPolygon seekPoly = GetPolys(params[0]).get(0);
				CPolygon areaPoly = GetPolys(params[1]).get(0);
				
				if(seekPoly.IsRectangle() && areaPoly.IsRectangle())
				{
					CRectangle findRec = seekPoly.ToRectangles().get(0);
					CRectangle areaRec = areaPoly.ToRectangles().get(0);
					LinkedList<CRectangle> recsInArea = null;
					
					if(params[2].equalsIgnoreCase("all"))
						recsInArea = CGeometricOperations.GetRectanglesInArea(areaRec, m_parseManager.getRecsList());
					else
						recsInArea = CGeometricOperations.GetRectanglesInArea(areaRec, m_layoutManager.GetRecsInLayer(Layer.values()[Integer.valueOf(params[2].trim())]));
					
					CRectangle placeRec = WhiteSpaceReport.WhiteSpaceByRect.findMinEnclosing(
							findRec, areaRec, recsInArea);
					
					if(placeRec == null)
					{
						PrintMessage("No free space for " + findRec.toString() + " in " + areaRec.toString());
						return false;
					}
					
					PrintCommandResult(PLACE_CMD_STR, "Placement space is " + placeRec.toString());					
					return false;
				}
			}
			catch (Exception e) 
			{}
		}
		
		MakeBadCommand();
		return false;
	}


	private boolean MakePerimCommand(String command) 
	{
		if(ValidateCommand(command, PERIM_CMD_STR))
		{
			try 
			{
				CPolygon poly = GetPolys(command).get(0);
				
				PrintCommandResult(PERIM_CMD_STR, "Polygon's perimiter is: " + poly.GetPerimiter());
				
				return false;
			}
			catch (Exception e) 
			{}
		}
		
		MakeBadCommand();
		return false;
	}


	private boolean MakeSpaceCommand(String command) 
	{
		if(ValidateCommand(command, SPACE_CMD_STR))
		{
			try 
			{
				CPolygon poly = GetPolys(command).get(0);
				
				PrintCommandResult(SPACE_CMD_STR, "Polygon's space is: " + poly.GetArea());
				
				return false;
			}
			catch (Exception e) 
			{}
		}
		
		MakeBadCommand();
		return false;
	}


	private boolean MakeBDCommand(String command) 
	{
		if(ValidateCommand(command, BD_CMD_STR))
		{
			LinkedList<CRectangle> recsList = new LinkedList<CRectangle>();

			try{
				LinkedList<CPolygon> polysList = GetPolys(command);

			    for (CPolygon polygon : polysList) {
					recsList.addAll(polygon.ToRectangles());
				} 
			    
				CRectangle boundingBox = CGeometricOperations.GetBoundingBox(recsList);
				
				PrintCommandResult(BD_CMD_STR, boundingBox.toString());
				
				return false;
			}
			catch (Exception e) 
			{}
		}
		
		MakeBadCommand();
		return false;
	}
	
	private boolean MakeManCommand(String command)
	{
		if(ValidateCommand(command, MAN_CMD_STR))
		{
			PrintMessage("\n" + PACKAGE_NAME + " user manual:\n" +
					DRC_CMD_STR + " [path]                                       'design rule check'\n" +
					BD_CMD_STR + " [set of polygons]                             'bounding box'\n" +
					SPACE_CMD_STR + " [polygon]                                  'space of polygon'\n" +
					PERIM_CMD_STR + " [polygon]                                  'perimeter of polygon'\n" +
					UNION_CMD_STR + " [set of polygons]                          'union'\n" +
					NEIGHB_CMD_STR + " [polygon]                                 'neighbours'\n" +
					CROSSCAP_CMD_STR + " [layer] ; [cap const]                   'cross capacitance'\n" +
					PLACE_CMD_STR + " [box] ; [area] ; [layer]                   'placement'\n" +
					WHITE_CMD_STR + " [area] ; [layer] ; [direction]             'white spaces'\n" +
					CONNECT_CMD_STR + " [point1] ; [layer] ; [point2] ; [layer]  'connectivity'\n" +
					EXIT_CMD_STR + "                                             'exit'\n" +
					"\n*  separate points by ',' and polygons/params by ';'\n");
		}
		else
		{
			MakeBadCommand();
		}
		
		return false;
	}

	
	/***************************************************/
	/****** MAIN ************************************/
	/***************************************************/
	/**
	 * Software package for layout operations
	 * 
	 * @param args
	 * First argument - textual layout source (URL or Path)
	 */
	public static void main(String[] args) 
	{
		CLIManager cliMan = new CLIManager();
		
		if(cliMan.ParseCommandLine(args))
			return;

		cliMan.ActivateShell();
	}
}
